#include <Windows.h>
#include <WinBase.h>
#include <iostream>
#include <map>
#include <fstream>

#include "yaml-cpp/yaml.h"

#include "rapidjson/document.h"
#include "rapidjson/stringbuffer.h"
#include "rapidjson/writer.h"

#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define random(x) (rand()%x)

#include "CMQTTClient.h"

#define client_id "ngrok_client"

#define ADDRESS "tcp://116.85.22.108:1883"
#define PUB_TOPIC "ngrokserver/"
#define SUB_TOPIC "ngrokserver_result/"
#define TOPIC "TEST"
#define CLIENTID "localhostSub"
#define PAYLOAD     "Hello World*****"
#define QOS         1
#define QOS_SUB     0
#define TIMEOUT     10000L

using namespace std;
using namespace rapidjson;

typedef map<string, int> Service_Port_Map;		//用于保存服务名和端口
Service_Port_Map g_service_port_map;

int g_pub_delivered = 0;	//1:已送达 0:未送达

void init_cmd()
{	
	char cmdclsname[] = "ConsoleWindowClass";
	HWND h = FindWindow(cmdclsname, NULL);
	HRGN hRgn = CreateRectRgn(0, 0, 500, 500);
	SetWindowRgn(h, hRgn, true);
}

void parseYaml(Service_Port_Map& server_port_map)
{
	YAML::Node config = YAML::LoadFile("ngrok.cfg");

	if (config["server_addr"])
	{
		std::cout << "server_addr" << config["server_addr"].as<std::string>();
	}

	YAML::Node tunnels = config["tunnels"];

	for (YAML::const_iterator it = tunnels.begin(); it != tunnels.end(); ++it)
	{	
		int port = 0;
		std::string service = it->first.as<std::string>();
		
		YAML::Node subnode = tunnels[service];
		port = subnode["remote_port"].as<int>();

		server_port_map.insert(pair<string, int>(service, port));
	}
}

string createJson(Service_Port_Map service_port_map, string clientid)
{
	Document doc;
	Document::AllocatorType &allocator = doc.GetAllocator();
	doc.SetObject();

	Value _client_id;
	_client_id.SetString(clientid.c_str(), clientid.size(), allocator);
	doc.AddMember("clientID", _client_id, allocator);

	Value port_info_array(kArrayType);	//生成一个数组
	Service_Port_Map::iterator itr = service_port_map.begin();
	for (; itr != service_port_map.end(); ++itr)
	{	
		int port = itr->second;
		string service_name = itr->first;

		Value port_info(kObjectType);
		Value name;
		name.SetString(service_name.c_str(), service_name.size(), allocator);
		port_info.AddMember("port", port, allocator);
		port_info.AddMember("serviceName", name, allocator);

		port_info_array.PushBack(port_info, allocator);
	}

	doc.AddMember("portInfo", port_info_array, allocator);

	//生成string
	StringBuffer buffer;
	Writer<StringBuffer> writer(buffer);
	doc.Accept(writer);

	string result = buffer.GetString();

	return result;
}

int parseJson(string json, Service_Port_Map& service_port_map)
{
	StringStream s(json.c_str());
	Document doc;
	doc.ParseStream(s);

	if (doc.HasParseError())
	{
		cout << "Parse json error" << endl;
		return -1;
	}

	if (doc.HasMember("portInfo"))
	{
		const Value& portInfo = doc["portInfo"];
		for (size_t i = 0; i < portInfo.Size(); ++i)
		{	
			const Value& info = portInfo[i];
			const Value& port = info["port"];
			const Value& service_name = info["serviceName"];

			int m_port = port.GetInt();
			string m_service_name = service_name.GetString();

			Service_Port_Map::iterator it = g_service_port_map.find(m_service_name);
			if (it != g_service_port_map.end())
			{
				it->second = m_port;
			}
		}
	}

	return 0;
}

void delivered(void *context, MQTTClient_deliveryToken dt)
{
	printf("Message with token value %d delivery confirmed\n", dt);
	g_pub_delivered = 1;
}

void changeYaml(Service_Port_Map service_port_map)
{
	YAML::Node config = YAML::LoadFile("ngrok.cfg");

	YAML::Node tunnels = config["tunnels"];

	Service_Port_Map::iterator it = service_port_map.begin();
	for (; it != service_port_map.end(); ++it)
	{
		if (tunnels[it->first])
		{
			YAML::Node node = tunnels[it->first];
			if (node["remote_port"])
			{
				node["remote_port"] = it->second;
			}
		}
	}

	ofstream out("ngrok.cfg");
	out << config;
}

int msgarrvd(void *context, char *topicName, int topicLen, MQTTClient_message *message)
{
	int i;
	char* payloadptr;

	printf("Message arrived\n");
	printf("     topic: %s\n", topicName);
	printf("   message: ");

	payloadptr = (char*)message->payload;
	for (i = 0; i < message->payloadlen; i++)
	{
		putchar(*payloadptr++);
	}
	putchar('\n');
	string data = (char*)message->payload;
	data = data.substr(0, message->payloadlen);

	parseJson(data, g_service_port_map);

	changeYaml(g_service_port_map);
	WinExec("cmd /k runGosuncnNgrok.bat", SW_SHOWNORMAL);
	MQTTClient_freeMessage(&message);
	MQTTClient_free(topicName);
	return 1;
}

void connlost(void *context, char *cause)
{
	printf("\nConnection lost\n");
	printf("     cause: %s\n", cause);
}

void mqttclientPubConfig(CMQTTClent& client)
{
	client.conf.address = ADDRESS;
	client.conf.clientID = CLIENTID;
	client.conf.qos = QOS;
	client.conf.topic = TOPIC;
	client.conf.timeout = TIMEOUT;
	client.conf.type = PUBLISH;
	//client.conf.type = SUBSCRIBE;
	client.conf.keepAliveInterval = 20;
	client.conf.cleanSession = 1;
	client.conf.connlostCB = &connlost;
	client.conf.deliverycompleteCB = &delivered;
	client.conf.msgCB = NULL;

	client.init();
}

void mqttclientConfig(CMQTTClent& client,int type)
{
	client.conf.address = ADDRESS;
	srand((int)time(0));
	int random_num = random(100);
	client.conf.clientID = client_id + to_string(random_num);
	client.conf.qos = QOS;
	//client.conf.topic = TOPIC;
	client.conf.timeout = TIMEOUT;
	//client.conf.type = PUBLISH;
	//client.conf.type = SUBSCRIBE;
	client.conf.keepAliveInterval = 20;
	client.conf.cleanSession = 1;
	client.conf.connlostCB = &connlost;
	client.conf.deliverycompleteCB = &delivered;
	//client.conf.msgCB = NULL;
	
	string clientid = client_id;
	if (type == PUBLISH)
	{
		//client.conf.qos = QOS;
		client.conf.type = PUBLISH;
		client.conf.topic = PUB_TOPIC + clientid;
		client.conf.msgCB = NULL;
	}
	else if (type == SUBSCRIBE)
	{
		//client.conf.qos = QOS_SUB;
		client.conf.type = SUBSCRIBE;
		client.conf.topic = SUB_TOPIC + clientid;
		client.conf.msgCB = &msgarrvd;
	}

	client.init();
}

int main()
{	
	//init_cmd();

	parseYaml(g_service_port_map);

	string json = createJson(g_service_port_map, client_id);

	CMQTTClent pub_client;
	//CMQTTClent sub_client;
	//mqttclientPubConfig(pub_client);
	mqttclientConfig(pub_client, PUBLISH);
	//mqttclientConfig(sub_client, SUBSCRIBE);
	pub_client.publish((char*)json.c_str(), json.size(), 0);

	//while (g_pub_delivered == 0);	//等待MQTT推送成功

	CMQTTClent sub_client;
	mqttclientConfig(sub_client, SUBSCRIBE);
	sub_client.startSub();

	getchar();
	pub_client.mqttDestory();
	//sub_client.mqttDestory();

	return 0;
}